﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using SharedGenomics.Core;
using SharedGenomics.Annotations;
using SharedGenomics.DataTransfer;
using SharedGenomics.Workbench.Core;
using SharedGenomics.Workbench.Annotations;
using SharedGenomics.Workbench.Annotations.Requests;
using SharedGenomics.Workbench.Services;
using SharedGenomics.Workbench.Tests.TestObjects;

using Rhino.Mocks;

namespace SharedGenomics.Workbench.Tests.Annotations.Requests
{
    [TestClass]
    public class NcbiAnnotationRequestTests
    {
        private MockRepository mocks;
        private IAnnotationService annotationService;
        private NcbiAnnotationRequest request;

        [TestInitialize]
        public void Setup()
        {
            this.mocks = new MockRepository();
            this.annotationService = this.mocks.Stub<IAnnotationService>();
            this.request = new NcbiAnnotationRequest(this.annotationService);
        }

        [TestMethod]
        public void ShouldExecuteRequests()
        {
            var ncbiInfos = new[]
            {
                new NcbiInfo { MarkerName = "rs1", EntrezGeneId = 1, BasePosition = 10000, Chromosome = 1, GeneName = "gene1" },
                new NcbiInfo { MarkerName = "rs2", EntrezGeneId = 2, BasePosition = 20000, Chromosome = 2, GeneName = "gene2" },
                new NcbiInfo { MarkerName = "rs3", EntrezGeneId = 3, BasePosition = 30000, Chromosome = 3, GeneName = "gene3" }
            };

            this.annotationService.Stub(s => s.GetNcbiInfos(null)).IgnoreArguments().Return(ncbiInfos.Just());
            this.mocks.ReplayAll();

            var contexts = this.ExecuteRequests(50);

            //2nd-4th contexts should have ncbi annotations
            Assert.IsTrue(contexts.Skip(1).Take(3).All(c => c.Results.First().Annotation != null));
        }

        [TestMethod]
        public void ShouldFailRequestsIfServiceReturnsNothing()
        {
            this.annotationService.Stub(s => s.GetNcbiInfos(null)).IgnoreArguments().Return(new Maybe<NcbiInfo[]>());
            this.mocks.ReplayAll();

            var contexts = this.ExecuteRequests(50);
            Assert.IsTrue(contexts.All(c => c.Results.First().Status == AnnotationRequestStatus.Failed));
        }

        [TestMethod]
        public void ShouldFailRequestsIfServiceThrows()
        {
            this.annotationService.Stub(s => s.GetNcbiInfos(null)).IgnoreArguments().Throw(new System.ServiceModel.CommunicationException());
            this.mocks.ReplayAll();

            var contexts = this.ExecuteRequests(50);
            Assert.IsTrue(contexts.All(c => c.Results.First().Status == AnnotationRequestStatus.Failed));
        }

        [TestMethod]
        public void ShouldExecuteLeftoverRequests()
        {
            var ncbiInfos = new[]
            {
                new NcbiInfo { MarkerName = "rs1", EntrezGeneId = 1, BasePosition = 10000, Chromosome = 1, GeneName = "gene1" },
                new NcbiInfo { MarkerName = "rs2", EntrezGeneId = 2, BasePosition = 20000, Chromosome = 2, GeneName = "gene2" },
                new NcbiInfo { MarkerName = "rs3", EntrezGeneId = 3, BasePosition = 30000, Chromosome = 3, GeneName = "gene3" }
            };

            this.annotationService.Stub(s => s.GetNcbiInfos(null)).IgnoreArguments().Return(ncbiInfos.Just());
            this.mocks.ReplayAll();

            //NOTE: the number of requests is not enough to trigger the request directly so they must be executed by the timer event
            var contexts = this.ExecuteRequests(62);

            //2nd-4th contexts should have ncbi annotations
            Assert.IsTrue(contexts.Skip(1).Take(3).All(c => c.Results.First().Annotation != null));
        }

        private List<AnnotationRequestContext> ExecuteRequests(int requestCount)
        {
            var traversalStrategies = requestCount.Of(i => new TestAnnotationRequestPipelineTraversalStrategy()).ToList();
            var contexts = requestCount.Of(i => new AnnotationRequestContext(AnnotationKey.NcbiSummary, "rs" + i)).ToList();

            ParameterizedThreadStart threadFunc = si =>
            {
                int startIndex = (int)si;
                int count = Math.Min(10, requestCount - startIndex);
                Enumerable.Range(startIndex, count).ForEach(i => this.request.Process(contexts[i], traversalStrategies[i]));
            };

            //create some thread, each of which will make up to 10 requests
            int threadCount = requestCount % 10 == 0 ? requestCount / 10 : requestCount / 10 + 1;
            var threads = threadCount.Of(i => new Thread(threadFunc)).ToList();
            threadCount.Times(i => threads[i].Start(i * 10));

            //wait for requests to finish
            threads.ForEach(t => t.Join());

            //wait for all requests to be continued
            while (!traversalStrategies.All(s => s.MovedNext)) { Thread.Sleep(100); }

            return contexts;
        }
    }
}
